CRXJS Vite Pluginを使って爆速でChrome拡張機能を作るチュートリアル(V3対応)
どうも。 えーたん(@eetann092)です。
先日社内LTに登壇しました。 タイトルは「Chrome拡張機能の開発がやめられねぇ」です。
今回はその発表内容を一部抜粋し、CRXJS Vite Pluginを使ったChrome拡張機能の作り方をチュートリアル形式で紹介します。
完成形を見る
このチュートリアルでは、「ユーザーが入力した文字列と一致するハッシュタグ」がブックマーク名にある時に、まとめてタブとして開く拡張機能を作ります。
たとえば、以下のようなブックマークがあるとします。
ブックマーク名として、ハッシュタグ#morning
, #zenn
などが含まれています。
ここで、ブラウザの右上にある拡張機能のアイコンをクリックすると、入力欄が開きます。
ユーザーが入力欄にmorning
と入力してEnterを押します。
すると、ハッシュタグ#morning
をブックマーク名に含む3つのブックマークがタブとして開かれます。
これと同じ機能を実装します。
インストール
今回は、ViteでReactとTypeScriptを入れて作ります。 CRXJS Vite pluginと、Chrome拡張機能固有の型を追加する @types/chromeも入れます。
ゼロから拡張機能の開発をすると、コードを書き換えるたびに管理画面の更新ボタンを押す必要があります。 しかし、CRXJS Vite pluginを使えば自動で再読み込みされるため、更新ボタンを手動で押す手間が省けます。
また、manifest.json
という拡張機能固有のファイルをTypeScriptを使って書くこともできます。
実際にファイルを作成していきます。まず、Viteのプロジェクトを作成します。
npm init vite@latest
プロジェクト名はopen-bookmarks
にしました。
途中、React(react
)とTypeScript(react-ts
)を選択します。
次に、作成されたディレクトリに移動してパッケージをインストールします。
cd open-bookmarks npm install npm i @crxjs/vite-plugin -D npm i @types/chrome
vite.config.tsの更新
最初にvite.config.ts
を書き換えます。
以下が書き換え後のvite.config.ts
の全体です。
import { crx, defineManifest } from "@crxjs/vite-plugin"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; const manifest = defineManifest({ manifest_version: 3, name: "Open Bookmarks", version: "1.0.0", permissions: ["bookmarks"], action: { default_popup: "index.html", }, }); export default defineConfig({ plugins: [react(), crx({ manifest })], });
CRXJS Vite Pluginのimport、manifestの定義、プラグインの読み込みを加えました。
manifestについて詳しく見ていきます。
CRXJS Vite PluginのdefineManifest
を使うことでmanifest.json
をTypeScriptで書いています。
manifest.json
は拡張機能固有のファイルです。拡張機能の名前やバージョン、パーミッションなどを書きます。
const manifest = defineManifest({ manifest_version: 3, name: "Open Bookmarks", version: "1.0.0", permissions: ["bookmarks"], action: { default_popup: "index.html", }, });
今回のpermissions
には、ブックマークを取得するためのbookmarks
を指定しました。
これらを指定することで、chrome.bookmarks.hoge
のようなChrome拡張機能のAPIが使えます。
↓2022-09-21追記
公開当初はpermissionsにtabsを追加していましたが、chrome.tabs.create
であればこのpermissionは指定しなくても動くようです(参考:chrome.tabs - Chrome Developers)。
manifest_version
には3
を指定します。このmanifest自体のバージョンによって拡張機能の書き方が違います。
以前のバージョンである2
の情報が多いため、記事やコードを参考にする際はmanifest_version
が3
であるか注意しましょう。
action
では、拡張機能のアイコンをクリックしたらindex.html
をポップアップ表示するように指定しました。
動かしてみよう
vite.config.ts
を書き換えた時点で、拡張機能として動かすことができるため、試してみます。
まずは以下のコマンドを実行します。
npm run dev
次に、拡張機能の管理ページを開きます。
chrome://extensions/
にアクセスするか、ブラウザ右上のメニューより「その他のツール>拡張機能」をクリックします。
拡張機能の管理ページで、デベロッパーモードをオンにします。
「パッケージ化されていないされていない拡張機能を読み込む」のボタンを押します。
先程作ったプロジェクトのdistディレクトリを選択します。
ブラウザの右上に表示される拡張機能のアイコンをクリックすると、index.html
が表示されます。
これで拡張機能として動かせることは確認できました。
ブックマークの一覧を取得する
次は、拡張機能固有の処理を書いてみます。 まずはブックマークの一覧を取得します。
src/App.tsx
を以下のように書きまえました。確認のため、取得したブックマーク名を表示しています。
import { useEffect, useState } from "react"; const App = (): JSX.Element => { const [allBookmarks, setAllBookMarks] = useState< chrome.bookmarks.BookmarkTreeNode[] >([]); useEffect(() => { chrome.bookmarks.search({}, (bookmarkItems) => { setAllBookMarks(bookmarkItems.filter((item) => "url" in item)); }); }, []); return ( <div> {allBookmarks.map((bookmark) => { return <p key={bookmark.id}>{bookmark.title}</p>; })} </div> ); }; export default App;
1つずつ見ていきます。
const [allBookmarks, setAllBookMarks] = useState< chrome.bookmarks.BookmarkTreeNode[] >([]);
まず、useStateですべてのブックマークをstateに入れる準備をします。
useEffect(() => { chrome.bookmarks.search({}, (bookmarkItems) => { setAllBookMarks(bookmarkItems.filter((item) => "url" in item)); }); }, []);
useEffectでは、ブックマークを取得する処理を書きました。
chrome.bookmarks.search
を使い、第一引数に空のオブジェクトを指定することで、すべてのブックマーク情報を取得します。
ただし、取得したブックマークの情報bookmarkItems
にはブックマークフォルダの情報も含んでいます。そこで、ブックマークフォルダを除外するために、url
プロパティを含むもの(=フォルダ以外)だけにします。
最後に、取得したブックマークのタイトルを確認のために表示します。
<div> {allBookmarks.map((bookmark) => { return <p key={bookmark.id}>{bookmark.title}</p>; })} </div>
ブラウザの右上に表示される拡張機能のアイコンをクリックすると、ブックマークのタイトルがずらりと並んでいます。
指定したタグを持つブックマークを開く
指定したタグを持つブックマークをタブとして開けるように、さらにsrc/App.tsx
を書き換えます。
以下が書き換え後のsrc/App.tsx
の全体です。
import { useEffect, useRef, useState } from "react"; const App = (): JSX.Element => { const [allBookmarks, setAllBookMarks] = useState< chrome.bookmarks.BookmarkTreeNode[] >([]); const query = useRef<HTMLInputElement>(null); useEffect(() => { chrome.bookmarks.search({}, (bookmarkItems) => { setAllBookMarks(bookmarkItems.filter((item) => "url" in item)); }); }, []); return ( <div> <input autoFocus={true} type="text" ref={query} onKeyPress={(e) => { if (e.key === "Enter") { const regexp = new RegExp(`#${query.current?.value}(\\s|$)`); const bookmarks = allBookmarks.filter((item) => regexp.test(item.title) ); for (const bookmark of bookmarks) { chrome.tabs.create({ url: bookmark.url }); } } }} /> </div> ); }; export default App;
1つずつ見ていきます。
const query = useRef<HTMLInputElement>(null);
まず、useRefで入力欄の値を取得できるようにしました。
<input autoFocus={true} type="text" ref={query} onKeyPress={(e) => { if (e.key === "Enter") { const regexp = new RegExp(`#${query.current?.value}(\\s|$)`); const bookmarks = allBookmarks.filter((item) => regexp.test(item.title) ); for (const bookmark of bookmarks) { chrome.tabs.create({ url: bookmark.url }); } } }} />
inputタグでは、Enterが押されたときにブックマークを開く処理をしています。
const regexp = new RegExp(`#${query.current?.value}(\\s|$)`); const bookmarks = allBookmarks.filter((item) => regexp.test(item.title) );
ブックマーク名(ここではitem.title
)にユーザーの入力query
を含むものだけに絞り込みます。
for (const bookmark of bookmarks) { chrome.tabs.create({ url: bookmark.url }); }
最後に、絞り込んだブックマークのURLをchrome.tabs.create
を使ってタブとして開きます。
ブラウザの右上に表示される拡張機能のアイコンをクリックすると、入力欄が表示されます。 Enterを押した時に「入力した文字列と一致するハッシュタグ」がブックマーク名に存在すると、タブとして開きます。
ショートカットキーを追加する
いちいち拡張機能のアイコンをクリックするのは時間がかかります。そこで、ショートカットキーで入力欄が表示されるようにします 。 ショートカットキーを追加するには、manifestを書き換えます。
以下が書き換え後のvite.config.ts
の全体です。
import { crx, defineManifest } from "@crxjs/vite-plugin"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; const manifest = defineManifest({ manifest_version: 3, name: "Open Bookmarks", version: "1.0.0", permissions: ["bookmarks"], action: { default_popup: "index.html", }, commands: { _execute_action: { suggested_key: { default: "Ctrl+Shift+Y", }, }, }, }); export default defineConfig({ plugins: [react(), crx({ manifest })], });
ショートカットキーはmanifestのcommands
に登録します。
本来はchrome.commands.onCommand.addListener
を使って実行されたコマンドの判定が必要です。しかし、今回のようにmanifestのaction
の処理をショートカットキーに登録する場合は、予約された名前_execute_action
に書くだけでよいです。
_execute_action
のsuggested_key
にOSごとにショートカットキーを登録できます。今回は共通のdefault
にしました。
上記の例ではCtrl+Shift+Y
と書いていますが、Macの場合はCtrl
がCommand
に変換されて登録されます。逆に、macOSのcontrolキーを指定する場合は、default
ではなくmac
、Ctrl
ではなくMacCtrl
と書いて指定します。
使えるキーや組み合わせなど、詳しくは公式ドキュメントを御覧ください。
実際に今回登録したキーを押すと、入力欄が開きます。また、拡張機能管理ページの左上のメニューより「キーボードショートカット」を開くと、登録したショートカットキーが確認できます。chrome://extensions/shortcuts
からもアクセスできます。
npm run dev
の実行を止めると、localhostとの接続が切れるためエラーになります(管理画面から確認できます)。
これ以上ソースコードを編集しない場合は、ここで一度ビルドし、管理画面の更新ボタンを押して再読み込みしましょう。
npm run build
以上でチュートリアルは終了です。今回はポップアップ表示だけでしたが、拡張機能には既存のページに処理を加えるContent Script
やbackground
、オプションページなどさまざまな機能があります。
以下のリンク集を参考に、ぜひオリジナルの拡張機能を作ってみてください。
リンク集
サンプルコードのGitHubのリンクは以下です。
以下、Chrome拡張機能開発で役に立ちそうなリンク集です。